# coding=utf-8

from urllib2 import HTTPError

import web
from web.utils import storage

from weibo import APIClient, APIError
from qqweibo.error import QWeiboError

import utils
import settings

# 用户数据接口
import models
# 社交连接接口
from sc import SC

__all__ = ('UserSession',)

# 处理信息
# 验证成功
OAUTH_SUCCESS = 'OK'
# 注册成功
# 只有当用户第一次连接时才会返回此状态
OAUTH_REGISTER_SUCCESS = 'REG OK'
# HTTP ERROR 400
# 一般时验证会话超时了
# 比如验证登录后，从微博返回到寻宝图时
# 若Code没有立即验证，而是等待一段时间后验证，
# 那么就会产生这个错误
OAUTH_400 = '非常抱歉，验证会话超时了，请重试'
# 调用 request_access_token() 方法时，
# 如果产生异常返回的错误
OAUTH_GET_TOKEN_ERROR = '[100]获取Token失败'
# 获取用户失败
# 调用 get_user_show 方法失败
OAUTH_GET_USER_ERROR = '[101]获取用户信息失败'
# 方法最后各种失败了
OAUTH_FAIL = '[255]登录失败，请重试'
# TODO: 表示一个用户会话对象
class UserSession(object):
    def __init__(self):
        # 用户在寻宝图上的id
        self.uid = 0
        # 用户名
        self.name = False
        # 社交连接的绑定id
        self.bind_id = 0
        # 用户的订阅Email
        self.email = False
        # 设定默认的连接对象
        self.sc = False
        # 当前会话的OAuth客户端对象
        self._client = False
        # 读取cookie
        self._resolve_cookie()

    # TODO: 获取cookie中的信息
    def _resolve_cookie(self):
        cookie = web.cookies()
        val = utils.decrypt(cookie.get('su', None))
        if val:
            vals = val.split('|')
            if len(vals) >= 2:
                # 至少要包括 uid 和使用的社交连接类型
                # [0] as uid
                # [1] as 社交连接类型
                # [4] as 社交连接的绑定id
                try:
                    self.uid = long(vals[0])
                except:
                    self.uid = 0
                # 社交连接的绑定id
                self.bind_id = vals[4]
                # 创建社交连接对象
                self._client = SC().get(vals[1])
                if self._client:
                    # 如果创建出有效的社交连接api实例，那么更新默认的sc类型
                    self.sc = vals[1]
                    # 让client去解析cookie中剩余的数据
                    self._client.resolve_cookie(vals)

            # 获取保存在cookie中的昵称
            self.name = cookie.get('name', 'N/A')
        # 订阅标记
        val = utils.decrypt(cookie.get('sue', None))
        if val:
            vals = val.split('|')
            if len(vals) == 3:
                self.email = vals[1]

    def _is_subscribed(self):
        """
        是否已经订阅
        """
        return self.email and utils.is_email(self.email)

    # TODO: 表示当前会话用户是否已经订阅
    subscribed = property(_is_subscribed)

    def _is_bot(self):
        """
        来访用户是否是机器人
        """
        bots = ('baiduspider','googlebot','yahoo!','iaskspider',
                'sogou web spider','sogou push spider','yodaobot',
                'msnbot',)
        if web.ctx and web.ctx.env:
            agent = web.ctx.env.get('HTTP_USER_AGENT', None)
            if agent:
                agent = agent.lower()
                for bot in bots:
                    if bot in agent:
                        return True
        return False

    # 表示当前会话用户是否是搜索引擎机器人
    is_bot = property(_is_bot)

    # TODO: 表示当前会话用户是否登录
    def _is_logined(self):
        if self.uid > 0:
            if self._client:
                return self._client.logined()
            return True
        return False

    # TODO: 表示当前会话用户是否登录
    logined = property(_is_logined)

    # TODO: 退出当前会话用户的登录状态
    def logout(self, redir = '/'):
        # 清空cookie
        web.setcookie('su', None)
        web.setcookie('name', None)
        if redir:
            # 转向到给定页
            raise web.seeother(redir)

    # TODO: 获取当前会话的用户
    def resolve_info(self):
        if self.logined:
            return models.get_user_bind_by_id(self.bind_id
                    , self.client.name)
        else:
            return None

    # TODO: 创建微博客户端
    def _create_client(self):
        # 当社交连接对象未被创建，
        # 且有有效的社交连接类型时才创建
        if not self._client and self.sc:
            self._client = SC().get(self.sc)
        return self._client

    client = property(_create_client)

    # TODO: OAuth验证成功后保存登录状态
    # @code as string 社交连接登录返回的令牌码
    # @sclient as SC Object 社交连接的对象的实例
    def oauth_save_login(self, code, sclient):
        # 获取授权用户信息
        try:
            token = sclient.request_access_token(code)
        except APIError, e:
            return OAUTH_GET_TOKEN_ERROR
        except QWeiboError, e:
            return OAUTH_GET_TOKEN_ERROR
        except HTTPError, e:
            return OAUTH_400
        if token:
            # 先从微博获取用户信息
            sclient.set_access_token(token)
            # 获取微博用户信息
            wbusr = sclient.get_user_show(uid = token.uid)
            if not wbusr:
                # 如果对应的用户不存在
                # 直接失败
                return OAUTH_GET_USER_ERROR

            # 获取数据库中用户信息
            usr = models.get_user_by_bind_id(token.uid, sclient.name)
            if usr:
                # 如果找到相关用户，那么更新一下数据库中的信息
                models.update_user_from_weibo_callback(usr.uid
                        , wbusr
                        , sclient.name
                        , token)
                # 这里直接更新返回得用户对象，避免修改后再读一次数据库
                usr.screen_name = wbusr.screen_name
            else:
                # 没有获取到用户
                # 自动进行注册
                # 添加到数据库中
                #usr = models.add_user_from_weibo_callback(wbusr
                #        , sclient.name
                #        , token)
                # 这里不自动添加到数据库中
                # 设置连接类型
                token.type = sclient.name
                # 存入注册所需的临时信息到cookie中
                UserSession.save_reg_cookie(wbusr, token)
                # 返回注册标识
                return OAUTH_REGISTER_SUCCESS

            if usr:
                # 数据库添加成功
                # 写入cookie
                sclient.save_cookie(usr.id, token)
                # 写入用户昵称到cookie方便到处调用
                # 这里就不加密了
                web.setcookie('name', usr.screen_name)
                # 设置相关值到当前会话实例中
                self.uid = usr.id
                self.name = usr.screen_name
                self.bind_id = token.uid
                # 返回成功信息
                return OAUTH_SUCCESS
        return OAUTH_FAIL

    @staticmethod
    def save_login_cookie(sclient, wbusr, token):
        """设置登录状态"""
        if sclient:
            # 如果sclient是字符串
            # 认为其是连接客户端的类型
            if isinstance(sclient, (str, unicode)):
                sclient = SC().get(str(sclient))

            if sclient:
                sclient.save_cookie(wbusr['id'], token)
        else:
            # 如果未传入sclient
            utils.encrypt_cookie('su', '%s||||' % wbusr['id'])

        # 将用户的名称存入cookie
        web.setcookie('name', wbusr['screen_name'])

    @staticmethod
    def clear_reg_cookie():
        """
        清空注册绑定所需的临时数据
        """
        web.setcookie('_tok', None)
        web.setcookie('_tok_usr', None)

    @staticmethod
    def resolve_reg_cookie():
        """
        获取注册所需信息的临时数据
        """
        token = utils.decrypt_cookie('_tok')
        usr = utils.decrypt_cookie('_tok_usr')
        if not (token and usr):
            return False

        return storage(token = utils.json_decode(token)
                    , user = utils.json_decode(usr))

    @staticmethod
    def save_reg_cookie(wbusr, token):
        """
        保存注册所需信息的临时数据到cookie中
        """
        # 直接将连接标识加密存储到COOKIE
        utils.encrypt_cookie('_tok', utils.json_encode(token))
        # 将绑定的用户信息加密存储到COOKIE
        utils.encrypt_cookie('_tok_usr', utils.json_encode(wbusr))

    # TODO: 获取当前会话用户（静态方法）
    @staticmethod
    def current():
        return UserSession()

    def save_subscribed(self, email):
        """保存订阅会话状态"""
        # 保存一年
        web.setcookie('sue'
                , utils.encrypt('TiAn|%s|zHeN' % email)
                , expires = 60 * 60 * 24 * 365)

    def clear_subscribed(self):
        """清除订阅会话状态"""
        web.setcookie('sue', None)

def auth_subscribe(func):
    """
    验证用户是否已经订阅的装饰器
    """
    def proxy(iself, *args, **kw):
        session = UserSession.current()
        if session.logined and models.subscribed_by_uid(session.uid):
            # 如果用户已登录，且已订阅
            return func(True, *args, **kw)
        #elif session.subscribed and models.subscribed_by_email(session.email):
        # 这里不判断订阅会话是有效
        # 只要有订阅状态就行了（即使没有登录）
        elif session.subscribed:
            # 如果用户的订阅会话有效
            return func(True, *args, **kw)
        else:
            # 如果没有登录或没有已订阅
            return func(False, *args, **kw)
            # 那么跳转到订阅页
            #raise web.seeother('/subscribe?from=%s' % utils.urlencode(web.ctx.fullpath))

    return proxy

def auth_login(lite = False, ajax = False, url = '/'):
    '''
    验证登录状态的装饰器，用到需要登录的视图中
    @lite as bool, 是否简洁模式，即在弹出层的iframe中打开
    @ajax as bool, 是否来自ajax异步调用视图
    @url as string, lite = True时生效，没有登录时，跳转到的页面
    '''
    def proxy(func):
        def in_proxy(iself, *args, **kw):
            session = UserSession.current()
            if session.logined:
                return func(iself, *args, **kw)
            else:
                if ajax:
                    return 'UNKNOWN_USER'
                elif lite:
                    # 因为后台是采用的框架结构
                    # 因此在无效登录进行跳转时，使用得JS跳转
                    return '<script type="text/javascript">top.$.login();</script>'
                else:
                    return web.seeother(url)
                    
        return in_proxy
    return proxy

# 存入一个获取当前登录用户到模板引擎中
web.template.Template.globals['current_user'] = UserSession.current
